feat(parser): weave is an expression, not only a statement (#88 B) - #93
Conversation
Four of the five unparsed conformance/valid programs (v02_weave,
v08_crossing, v09_twist, v12_weave_expr) use weave as a DEFINITION BODY:
def simple_crossing =
weave strands a:Q, b:Q into
(a > b)
yield strands b:Q, a:Q
The grammar only allowed it as a top-level statement, so all four failed to
parse. Typed strands and `yield strands` were never the problem — those
already worked; only the position did.
## Why the expression form is the right fix
As a statement, weave is INERT. Evaluation is a no-op returning (env, None),
and the typechecker computes its Tangle[A,B] and then discards it:
let _weave_ty = TTangle (input_boundary, output_boundary) in
gamma
So the construct could be written but never bound, never used, never
observed. Binding it to a name is the only thing that makes it reachable —
which is exactly what the conformance corpus has always assumed.
The statement form is kept, so this is purely additive.
## Scope
Weave is OUTSIDE the mechanised core: 0 occurrences in proofs/Tangle.lean and
0 in the TG-3 corpus. So this touches no proof obligation, and Weave is listed
with the other non-core constructors in tg3_emit's explicit rejection branch.
Threaded through ast (new `Weave of weave_block`), parser (primary_expr),
typecheck (types as the Tangle[A,B] it denotes; body checked in the strand
context as the statement form does), eval (the body IS the value), and pretty
(prints in re-parseable form). `expr_calls` also traverses into a weave body,
or recursion inside one would be invisible to the #91 fixpoint search.
Two strand printers moved above pp_expr — they had no dependency on it and
pp_expr now needs them.
## What is NOT fixed: v11_add_block
`add{ 1 + 2 + 3 }` is not a missing parse rule. It is the Harvard DATA
SUB-LANGUAGE: its own expression grammar, its own type system (Int / Float /
Rational / Hex / Binary / Bool / String / Symbolic), its own environments (Pi)
and visibility rules, a separate typing judgement, Embed/Unembed between
Harvard and Tangle types, and bidirectional calling. 288 lines of
FORMAL-SEMANTICS.md across 12 sections. That is a feature, raised separately;
it stays in the corpus manifest as the single remaining known gap.
conformance: 14/19 -> 18/19. (It was reporting a fake 3/19 before #89 fixed
the runner.) Corpus manifest tightened 5 -> 1 — the ratchet demanded it, which
is what it is for.
Tests: weave as a definition body, the TG-4 round-trip property (pretty then
re-parse yields the same AST), and a guard that the statement form still
parses. spec/grammar.ebnf updated to match, with the rationale.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Note Automatic reviews are paused because your trial's included automatic processing has been used for this period. Upgrade now, or comment "Gitar review" to run a review anytime. Code Review ✅ Approved 1 resolved / 1 findingsExpands the parser to support weave as an expression, successfully parsing four conformance tests previously blocked by grammar limits. Consider addressing the minor finding where weave evaluation discards the yield output-strand reordering.
✅ 1 resolved✅ Edge Case: Weave evaluation discards the yield output-strand reordering
OptionsDisplay: compact → Showing less information. Comment with these commands to change the behavior for this request:
Was this helpful? React with 👍 / 👎 | Gitar |
|
|
…ertion (#97) The false assertion corrected in #95 was **not an isolated slip**. Auditing whether the conformance corpus actually *runs* — the gate only ever checked that it *parsed* — turned up a second mathematically false claim of exactly the same kind, plus one genuine unimplemented feature. ## The second false assertion `conformance/valid/v16_close_mirror_reverse.tangle`: ```tangle assert reverse(braid[s1, s2]) == braid[s2, s1] ``` `reverse` reverses the word **and** negates every exponent — it yields the *inverse* braid, so `reverse(s1 s2) = s2⁻¹ s1⁻¹`. Disproved by invariant: `writhe(reverse(braid[s1,s2])) = -2` while `writhe(braid[s2,s1]) = +2`, and writhe is invariant under the braid relations, so they cannot be the same element. ## Two authors, same mistake → a documentation failure `examples/trefoil.tangle` and this file made the *identical* wrong assumption. That is not carelessness. `FORMAL-SEMANTICS.md` described the operation as: ``` | reverse(e) -- reverse word ``` which reads as order-only. Now stated as an identity: ``` reverse(g₁ g₂ … gₙ) = gₙ⁻¹ … g₂⁻¹ g₁⁻¹ i.e. reverse(w) = w⁻¹ ``` with the contrast against `mirror` (which negates **in place**) spelled out, and the writhe argument recorded so the next reader can check it rather than trust it. ## The systemic fix `scripts/check-corpus.sh` now **evaluates** `conformance/valid`, not just parses it. Parsing was never enough — a program can parse perfectly and assert something false. Both bad assertions survived precisely because nothing ran them. **Verified in both directions:** reintroducing the exact false assertion makes the gate fail (`exit 1`, *"parses but does not evaluate"*); restoring it returns `exit 0`. ## The genuine gap `v09_twist.tangle` uses `(~a)` to twist a named strand inside a weave. `spec/grammar.ebnf` says *"In weave context: (~a) twists named strand a"*, but `infer_expr` rejects any strand name used as an expression — specified and unimplemented. Recorded in the new `CONFORMANCE_KNOWN_UNRUNNABLE` list rather than papered over, and raised as **#96**. Worth noting *why* it was invisible: until #93 a weave block could not be bound to anything, so its body was never evaluated. The construct has literally never run. ## Regression tests Four pinning `reverse`, chosen so they cannot pass vacuously: | Test | Why it can't be faked | |---|---| | reverse is **not** order-reversal | asserts inequality with the wrong answer | | `reverse(reverse(w)) = w` | only holds if it genuinely inverts | | reverse negates writhe | the invariant that disproved both false claims | | `mirror` negates *in place* | pins the contrast that caused the confusion | Suites 112 → 116, all green; TG-3 still 1008/0. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
) (#101) `conformance/valid/v09_twist.tangle` parsed but failed to typecheck: ```tangle def twisted = weave strands a:Q into (~a) yield strands a:Q ``` ``` Strand name 'a' cannot be used as a standalone expression ``` ## The spec licenses it explicitly `FORMAL-SEMANTICS.md` §3.10: ``` Σ(a) = (i, T) ─────────────────────────────────── [T-Twist-Strand] Γ; Σ ⊢ (~a) : Tangle[[T], [T]] ``` and `spec/grammar.ebnf`: *"In weave context: `(~a)` twists named strand a."* The `Twist` case inferred its operand as an ordinary expression, and the `Var` rule rejects strand names outright — so the strand-name guard fired before the twist rule could apply. `[T-Twist-Strand]` is now tried **first**, leaving `[T-Twist-Word]`/`[T-Twist-Tangle]` to the standalone forms. ## One of a matched pair was missing The spec presents this rule immediately alongside `[T-Self-Cross]`: ``` Σ(a) = (i, T) Σ(a) = (i, T) ───────────────────────────────── ───────────────────────────────── Γ; Σ ⊢ (~a) : Tangle[[T], [T]] Γ; Σ ⊢ (a > a) : Tangle[[T], [T]] ``` `[T-Self-Cross]` was already implemented; its twin was not. **A test now pins that the two agree**, so they can't drift apart again. ## Evaluation Mirrors `Crossing`: structural, yielding the empty word. Weave bodies only became evaluable in #93, so this construct had **never run**. That's also defensible mathematically — a single-strand twist is invisible to the braid *word*: it's a framing change (Reidemeister I) that the braid group doesn't see. Detecting it as "a `Var` the environment doesn't bind" is safe because typechecking runs first and admits `Twist (Var a)` only when `a` is a strand. ## Result conformance **16/19 → 17/19 evaluating** (18/19 parse). The corpus ratchet demanded the manifest update — exactly what it's for. **`v11_add_block` (#94, the Harvard sub-language) is now the only remaining gap.** Tests: the rule itself; agreement with `[T-Self-Cross]`; a regression guard that standalone twist on a `Word` is unchanged; and that a nonsense twist still fails. All suites green; corpus and RSR gates pass. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Four of the five unparsed
conformance/valid/programs —v02_weave,v08_crossing,v09_twist,v12_weave_expr— use weave as a definition body:The grammar allowed weave only as a top-level statement, so all four failed. Typed strands (
a:Q) andyield strandswere never the problem — those already parsed. Only the position did.Why the expression form is the right fix, not a corpus edit
My first instinct was that the corpus was wrong, since
spec/grammar.ebnfalso said statement. Then I checked what a weave statement actually does:As a statement, weave is inert. It binds nothing, produces nothing, and its type is thrown away. The construct could be written but never used. Binding it to a name is the only thing that makes it reachable — which is precisely what the corpus assumed all along.
The statement form is kept, so this is purely additive.
Scope — no proof implications
Weave is outside the mechanised core: 0 occurrences in
proofs/Tangle.lean, 0 in the TG-3 corpus. So this touches no proof obligation, andWeavejoins the other non-core constructors intg3_emit's explicit rejection branch rather than being translated.Threaded through
ast(newWeave of weave_block),parser(primary_expr),typecheck(types as theTangle[A,B]it denotes; body checked in the strand context exactly as the statement form does),eval(the body is the value), andpretty(re-parseable form).expr_callsalso traverses into a weave body — otherwise recursion inside one would be invisible to the #91 fixpoint search.What this does NOT fix:
v11_add_blockadd{ 1 + 2 + 3 }is not a missing parse rule. It is the Harvard data sub-language:&&/||/!, calls,if/then/elseΠ, with visibility rules⊢_hd288 lines of
FORMAL-SEMANTICS.mdacross 12 sections. That is a feature, raised separately. It remains the single entry in the corpus manifest's known-gap list.Results
(It reported a fake 3/19 before #89 fixed the runner.)
The ratchet in
check-corpus.shdemanded the manifest update — it failed the build with "now parses — drop it from CONFORMANCE_KNOWN_UNPARSED" for each file. That is exactly what it was built to do.Tests
pretty.mlFull suite green, 0 failures; TG-3 still 1008/0.
spec/grammar.ebnfupdated to match, with the rationale recorded.🤖 Generated with Claude Code